001 /*
002 * Copyright 2004-2005 Stephen McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.tools.tasks;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.io.FileInputStream;
025 import java.util.Enumeration;
026 import java.util.Properties;
027 import java.util.StringTokenizer;
028
029 import net.dpml.library.Type;
030 import net.dpml.library.Resource;
031 import net.dpml.library.info.Scope;
032
033 import net.dpml.tools.Context;
034
035 import net.dpml.transit.Transit;
036
037 import org.apache.tools.ant.BuildException;
038 import org.apache.tools.ant.Project;
039 import org.apache.tools.ant.taskdefs.Exit;
040 import org.apache.tools.ant.taskdefs.optional.junit.BatchTest;
041 import org.apache.tools.ant.taskdefs.optional.junit.FormatterElement;
042 import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
043 import org.apache.tools.ant.types.Environment;
044 import org.apache.tools.ant.types.FileSet;
045 import org.apache.tools.ant.types.Path;
046 import org.apache.tools.ant.types.Commandline;
047
048 /**
049 * JUnit test execution.
050 *
051 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
052 * @version 1.1.0
053 */
054 public class JUnitTestTask extends GenericTask
055 {
056 /**
057 * Constant for lookup of mx value.
058 */
059 public static final String MX_KEY = "project.test.mx";
060
061 /**
062 * Constant test enabled key.
063 */
064 public static final String TEST_ENABLED_KEY = "project.test.enabled";
065
066 /**
067 * Constant test src directory key.
068 */
069 public static final String TEST_SRC_KEY = "project.test.src";
070
071 /**
072 * Constant test env directory key.
073 */
074 public static final String TEST_ENV_KEY = "project.test.env";
075
076 /**
077 * Constant test debug key.
078 */
079 public static final String DEBUG_KEY = "project.test.debug";
080
081 /**
082 * Constant test fork key.
083 */
084 public static final String FORK_KEY = "project.test.fork";
085
086 /**
087 * Constant test fork mode key.
088 */
089 public static final String TEST_FORK_MODE_KEY = "project.test.fork.mode";
090
091 /**
092 * Constant test halt-on-error key (default is false).
093 */
094 public static final String HALT_ON_ERROR_KEY = "project.test.halt-on-error";
095
096 /**
097 * Constant test halt-on-failure key (default is false).
098 */
099 public static final String HALT_ON_FAILURE_KEY = "project.test.halt-on-failure";
100
101 /**
102 * Constant test abort on error key - if true (the default) the build will fail
103 * if a test error occurs.
104 */
105 public static final String ABORT_ON_ERROR_KEY = "project.test.exit-on-error";
106
107 /**
108 * Constant test abort on failure key - if true (the default) the build will fail
109 * if a test failure occurs.
110 */
111 public static final String ABORT_ON_FAILURE_KEY = "project.test.exit-on-failure";
112
113 /**
114 * Constant cache path key.
115 */
116 public static final String CACHE_PATH_KEY = "dpml.cache";
117
118 /**
119 * Constant work dir key.
120 */
121 public static final String WORK_DIR_KEY = "project.test.dir";
122
123 /**
124 * the key for the include pattern for test cases
125 */
126 public static final String TEST_INCLUDES_KEY = "project.test.includes";
127
128 /**
129 * default value
130 */
131 public static final String TEST_INCLUDES_VALUE = "**/*TestCase.java, **/*Test.java";
132
133 /**
134 * the key for the exclude pattern for test cases
135 */
136 public static final String TEST_EXCLUDES_KEY = "project.test.excludes";
137
138 /**
139 * default value
140 */
141 public static final String TEST_EXCLUDES_VALUE = "**/Abstract*.java, **/AllTest*.java";
142
143 /**
144 * the key for the exclude pattern for test cases
145 */
146 public static final String VERBOSE_KEY = "project.test.verbose";
147
148 private static final String ERROR_KEY = "project.test.error";
149 private static final String FAILURE_KEY = "project.test.failure";
150 private static final String TEST_SRC_VALUE = "test";
151 private static final String TEST_ENV_VALUE = "env";
152 private static final boolean DEBUG_VALUE = true;
153 private static final boolean FORK_VALUE = true;
154 private static final boolean HALT_ON_ERROR_VALUE = false;
155 private static final boolean HALT_ON_FAILURE_VALUE = false;
156
157 private File m_source;
158 private String m_classPathRef;
159 private Path m_classPath;
160
161 /**
162 * Task initialization.
163 * @exception BuildException if a build error occurs.
164 */
165 public void init() throws BuildException
166 {
167 if( !isInitialized() )
168 {
169 super.init();
170 final Project project = getProject();
171 project.setNewProperty( DEBUG_KEY, "" + DEBUG_VALUE );
172 project.setNewProperty( FORK_KEY, "" + FORK_VALUE );
173 project.setNewProperty( TEST_SRC_KEY, "" + TEST_SRC_VALUE );
174 project.setNewProperty( TEST_ENV_KEY, "" + TEST_ENV_VALUE );
175 project.setNewProperty( HALT_ON_ERROR_KEY, "" + HALT_ON_ERROR_VALUE );
176 project.setNewProperty( HALT_ON_FAILURE_KEY, "" + HALT_ON_FAILURE_VALUE );
177 getContext().getPath( Scope.TEST );
178 }
179 }
180
181 /**
182 * Set the id of the compilation classpath.
183 * @param id the classpath reference
184 */
185 public void setClasspathRef( String id )
186 {
187 m_classPathRef = id;
188 }
189
190 /**
191 * Set the classpath.
192 * @param path the classpath
193 */
194 public void setClasspath( Path path )
195 {
196 m_classPath = path;
197 }
198
199 /**
200 * Set the directory containing the unit test source files.
201 * @param source the test source directory
202 */
203 public void setSrc( File source )
204 {
205 m_source = source;
206 }
207
208 private Path getClasspath()
209 {
210 if( null != m_classPath )
211 {
212 return m_classPath;
213 }
214 else if( null != m_classPathRef )
215 {
216 return (Path) getProject().getReference( m_classPathRef );
217 }
218 else
219 {
220 final String error =
221 "Missing classpathref or classpath argument.";
222 throw new BuildException( error, getLocation() );
223 }
224 }
225
226 /**
227 * Task execution.
228 * @exception BuildException if a build error occurs.
229 */
230 public void execute() throws BuildException
231 {
232 if( !isTestingEnabled() )
233 {
234 return;
235 }
236
237 final Context context = getContext();
238 final Project project = getProject();
239 final File src = context.getTargetBuildTestDirectory();
240 if( src.exists() )
241 {
242 final File working = context.getTargetTestDirectory();
243 final Path classpath = getClasspath();
244
245 executeUnitTests( src, classpath, working );
246
247 if( getBooleanProperty( ABORT_ON_ERROR_KEY, true ) )
248 {
249 final String error = project.getProperty( ERROR_KEY );
250 if( null != error )
251 {
252 final String message =
253 "One or more unit test errors occured.";
254 fail( message );
255 }
256 }
257
258 if( getBooleanProperty( ABORT_ON_FAILURE_KEY, true ) )
259 {
260 final String failure = project.getProperty( FAILURE_KEY );
261 if( null != failure )
262 {
263 final String message =
264 "One or more unit test failures occured.";
265 fail( message );
266 }
267 }
268 }
269 }
270
271 private void executeUnitTests( final File src, final Path classpath, File working )
272 {
273 final Project project = getProject();
274 log( "Test classpath: " + classpath, Project.MSG_VERBOSE );
275 final FileSet fileset = createFileSet( src );
276 final JUnitTask junit = (JUnitTask) project.createTask( "junit" );
277 junit.setTaskName( getTaskName() );
278
279 final JUnitTask.SummaryAttribute summary = getSummaryAttribute();
280 junit.setPrintsummary( summary );
281
282 junit.setShowOutput( true );
283 junit.setTempdir( working );
284 junit.setReloading( true );
285 junit.setFiltertrace( true );
286
287 junit.createClasspath().add( classpath );
288
289 Context context = getContext();
290 String verbose = getVerboseArgument();
291 if( null != verbose )
292 {
293 Commandline.Argument arg = junit.createJvmarg();
294 arg.setValue( "-verbose:" + verbose );
295 }
296
297 final File reports = getContext().getTargetReportsTestDirectory();
298 mkDir( reports );
299
300 final BatchTest batch = junit.createBatchTest();
301 batch.addFileSet( fileset );
302 batch.setTodir( reports );
303
304 final FormatterElement plain = newConfiguredFormatter( "plain" );
305 junit.addFormatter( plain );
306
307 final FormatterElement xml = newConfiguredFormatter( "xml" );
308 junit.addFormatter( xml );
309
310 final Environment.Variable work = new Environment.Variable();
311 work.setKey( WORK_DIR_KEY );
312 work.setValue( working.toString() );
313 junit.addConfiguredSysproperty( work );
314
315 final Environment.Variable testBaseDir = new Environment.Variable();
316 testBaseDir.setKey( "project.test.dir" );
317 testBaseDir.setValue( working.toString() );
318 junit.addConfiguredSysproperty( testBaseDir );
319
320 final Environment.Variable targetDir = new Environment.Variable();
321 targetDir.setKey( "project.target.dir" );
322 targetDir.setValue( getContext().getTargetDirectory().toString() );
323 junit.addConfiguredSysproperty( targetDir );
324
325 final Environment.Variable deliverablesDir = new Environment.Variable();
326 deliverablesDir.setKey( "project.target.deliverables.dir" );
327 deliverablesDir.setValue( getContext().getTargetDeliverablesDirectory().toString() );
328 junit.addConfiguredSysproperty( deliverablesDir );
329
330 final Environment.Variable basedir = new Environment.Variable();
331 basedir.setKey( "basedir" );
332 basedir.setValue( project.getBaseDir().toString() );
333 junit.addConfiguredSysproperty( basedir );
334
335 final Environment.Variable basedir2 = new Environment.Variable();
336 basedir2.setKey( "project.basedir" );
337 basedir2.setValue( project.getBaseDir().toString() );
338 junit.addConfiguredSysproperty( basedir2 );
339
340 final Environment.Variable cache = new Environment.Variable();
341 cache.setKey( CACHE_PATH_KEY );
342 cache.setValue( getCachePath() );
343 junit.addConfiguredSysproperty( cache );
344
345 final File policy = new File( working, "security.policy" );
346 if( policy.exists() )
347 {
348 final Environment.Variable security = new Environment.Variable();
349 security.setKey( "java.security.policy" );
350 security.setValue( policy.toString() );
351 junit.addConfiguredSysproperty( security );
352 }
353
354 setupTestProperties( junit, project );
355
356 final File logProperties = new File( working, "logging.properties" );
357 if( logProperties.exists() )
358 {
359 final Environment.Variable log = new Environment.Variable();
360 log.setKey( "dpml.logging.config" );
361 try
362 {
363 log.setValue( logProperties.toURL().toString() );
364 }
365 catch( IOException e )
366 {
367 final String error =
368 "Unexpected file to url error."
369 + "\nFile: " + logProperties;
370 throw new BuildException( error, e );
371 }
372 junit.addConfiguredSysproperty( log );
373 }
374
375 String formatter = getResource().getProperty( "java.util.logging.config.class" );
376 if( null != formatter )
377 {
378 final Environment.Variable logging = new Environment.Variable();
379 logging.setKey( "java.util.logging.config.class" );
380 if( "dpml".equals( formatter ) )
381 {
382 logging.setValue( "net.dpml.util.ConfigurationHandler" );
383 }
384 else
385 {
386 logging.setValue( formatter );
387 }
388 junit.addConfiguredSysproperty( logging );
389 }
390
391 final Environment.Variable endorsed = new Environment.Variable();
392 endorsed.setKey( "java.endorsed.dirs" );
393 endorsed.setValue( new File( Transit.DPML_SYSTEM, "lib/endorsed" ).getAbsolutePath() );
394 junit.addConfiguredSysproperty( endorsed );
395
396 configureDeliverableSysProperties( junit );
397 configureForExecution( junit );
398
399 junit.setErrorProperty( ERROR_KEY );
400 junit.setFailureProperty( FAILURE_KEY );
401 final boolean haltOnErrorPolicy = getHaltOnErrorPolicy();
402 if( haltOnErrorPolicy )
403 {
404 junit.setHaltonerror( true );
405 }
406 final boolean haltOnFailurePolicy = getHaltOnFailurePolicy();
407 if( haltOnFailurePolicy )
408 {
409 junit.setHaltonfailure( true );
410 }
411
412 junit.init();
413 junit.execute();
414 }
415
416 private void setupTestProperties( JUnitTask junit, Project project )
417 {
418 File base = project.getBaseDir();
419 final File properties = new File( base, "test.properties" );
420 if( properties.exists() )
421 {
422 Properties props = new Properties();
423 try
424 {
425 InputStream input = new FileInputStream( properties );
426 props.load( input );
427 Enumeration enum = props.propertyNames();
428 while( enum.hasMoreElements() )
429 {
430 String name = (String) enum.nextElement();
431 final Environment.Variable v = new Environment.Variable();
432 v.setKey( name );
433 v.setValue( props.getProperty( name ) );
434 junit.addConfiguredSysproperty( v );
435 }
436 }
437 catch( IOException ioe )
438 {
439 final String error =
440 "Unexpected IO error while reading " + properties.toString();
441 throw new BuildException( error, ioe, getLocation() );
442 }
443 }
444 }
445
446 private void configureForExecution( JUnitTask task )
447 {
448 if( getForkProperty() )
449 {
450 task.setFork( true );
451 Project project = getProject();
452 task.setDir( project.getBaseDir() );
453 JUnitTask.ForkMode mode = getForkMode();
454 if( null == mode )
455 {
456 log( "Executing forked test." );
457 }
458 else
459 {
460 log( "Executing forked test with mode: '" + mode + "'." );
461 task.setForkMode( mode );
462 }
463 String mx = getContext().getProperty( MX_KEY );
464 if( null != mx )
465 {
466 task.setMaxmemory( mx );
467 }
468 }
469 else
470 {
471 log( "executing in local jvm" );
472 JUnitTask.ForkMode mode = new JUnitTask.ForkMode( "once" );
473 task.setForkMode( mode );
474 task.setFork( false );
475 }
476 }
477
478 private void configureDeliverableSysProperties( JUnitTask task )
479 {
480 try
481 {
482 Context context = getContext();
483 Resource resource = context.getResource();
484 Type[] types = context.getResource().getTypes();
485 for( int i=0; i<types.length; i++ )
486 {
487 Type type = types[i];
488 String id = type.getID();
489 File file = context.getTargetDeliverable( id );
490 String path = file.getCanonicalPath();
491 final Environment.Variable variable = new Environment.Variable();
492 variable.setKey( "project.deliverable." + id + ".path" );
493 variable.setValue( path );
494 task.addConfiguredSysproperty( variable );
495 }
496 }
497 catch( IOException ioe )
498 {
499 final String error =
500 "Unexpected IO error while building deliverable filename properties.";
501 throw new BuildException( error, ioe, getLocation() );
502 }
503 }
504
505 private FileSet createFileSet( File src )
506 {
507 final FileSet fileset = new FileSet();
508 fileset.setDir( src );
509 addIncludes( fileset );
510 addExcludes( fileset );
511 return fileset;
512 }
513
514 private void addIncludes( FileSet set )
515 {
516 String pattern = getTestIncludes();
517 log( "Test includes=" + pattern, Project.MSG_VERBOSE );
518 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false );
519 while( tokenizer.hasMoreTokens() )
520 {
521 String item = tokenizer.nextToken();
522 set.createInclude().setName( item );
523 }
524 }
525
526 private void addExcludes( FileSet set )
527 {
528 String pattern = getTestExcludes();
529 log( "Test excludes=" + pattern, Project.MSG_VERBOSE );
530 StringTokenizer tokenizer = new StringTokenizer( pattern, ", ", false );
531 while( tokenizer.hasMoreTokens() )
532 {
533 String item = tokenizer.nextToken();
534 set.createExclude().setName( item );
535 }
536 }
537
538 private String getTestIncludes()
539 {
540 String includes = getContext().getProperty( TEST_INCLUDES_KEY );
541 if( null != includes )
542 {
543 return includes;
544 }
545 else
546 {
547 return TEST_INCLUDES_VALUE;
548 }
549 }
550
551 private String getTestExcludes()
552 {
553 String excludes = getContext().getProperty( TEST_EXCLUDES_KEY );
554 if( null != excludes )
555 {
556 return excludes;
557 }
558 else
559 {
560 return TEST_EXCLUDES_VALUE;
561 }
562 }
563
564 private String getCachePath()
565 {
566 final String value = getContext().getProperty( CACHE_PATH_KEY );
567 if( null != value )
568 {
569 return value;
570 }
571 else
572 {
573 File cache = (File) getProject().getReference( "dpml.cache" );
574 return cache.toString();
575 }
576 }
577
578 private boolean getDebugProperty()
579 {
580 return getBooleanProperty( DEBUG_KEY, DEBUG_VALUE );
581 }
582
583 private boolean getForkProperty()
584 {
585 return getBooleanProperty( FORK_KEY, FORK_VALUE );
586 }
587
588 private JUnitTask.ForkMode getForkMode()
589 {
590 final String value = getContext().getProperty( TEST_FORK_MODE_KEY );
591 if( null == value )
592 {
593 return null;
594 }
595 else
596 {
597 return new JUnitTask.ForkMode( value );
598 }
599 }
600
601 private boolean getBooleanProperty( final String key, final boolean fallback )
602 {
603 final String value = getContext().getProperty( key );
604 if( null == value )
605 {
606 return fallback;
607 }
608 else
609 {
610 return Project.toBoolean( value );
611 }
612 }
613
614 private void fail( final String message )
615 {
616 final Exit exit = (Exit) getProject().createTask( "fail" );
617 exit.setMessage( message );
618 exit.init();
619 exit.execute();
620 }
621
622 private boolean isTestingEnabled()
623 {
624 final String enabled = getContext().getProperty( TEST_ENABLED_KEY, "true" );
625 return "true".equals( enabled );
626 }
627
628 private JUnitTask.SummaryAttribute getSummaryAttribute()
629 {
630 final Project project = getProject();
631 final JUnitTask.SummaryAttribute summary = new JUnitTask.SummaryAttribute();
632 summary.setValue( "on" );
633 return summary;
634 }
635
636 private boolean getHaltOnErrorPolicy()
637 {
638 return getBooleanProperty(
639 HALT_ON_ERROR_KEY, HALT_ON_ERROR_VALUE );
640 }
641
642 private boolean getHaltOnFailurePolicy()
643 {
644 return getBooleanProperty(
645 HALT_ON_FAILURE_KEY, HALT_ON_FAILURE_VALUE );
646 }
647
648 private String getVerboseArgument()
649 {
650 Context context = getContext();
651 return context.getProperty( "project.test.verbose" );
652 }
653
654 private FormatterElement newConfiguredFormatter( String type )
655 {
656 final FormatterElement formatter = new FormatterElement();
657 final FormatterElement.TypeAttribute attribute = new FormatterElement.TypeAttribute();
658 attribute.setValue( type );
659 formatter.setType( attribute );
660 return formatter;
661 }
662
663 }